package org.codefinger.json.serializer;

import java.io.IOException;
import java.io.Writer;
import java.lang.ref.SoftReference;

import org.codefinger.json.util.Base64;

public class SerializerWriter extends Writer {

	final static int[]										sizeTable		= { 9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, Integer.MAX_VALUE };

	final static char[]										DigitTens		= { '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', '9', '9', '9', '9', '9', '9', '9', '9', '9', '9', };

	final static char[]										DigitOnes		= { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', };

	final static char[]										digits			= { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };

	final static char[]										replaceChars	= new char[128];

	private final static ThreadLocal<SoftReference<char[]>>	bufLocal		= new ThreadLocal<SoftReference<char[]>>();

	private char											buf[];

	private int												count;

	private Writer											writer;

	static {
		replaceChars['\b'] = 'b'; // 8
		replaceChars['\t'] = 't'; // 9
		replaceChars['\n'] = 'n'; // 10
		replaceChars['\f'] = 'f'; // 12
		replaceChars['\r'] = 'r'; // 13
		replaceChars['\"'] = '"'; // 34
		replaceChars['\''] = '\''; // 39
		replaceChars['/'] = '/'; // 47
		replaceChars['\\'] = '\\'; // 92
	}

	public SerializerWriter() {
		SoftReference<char[]> ref = bufLocal.get();

		if (ref != null) {
			buf = ref.get();
			bufLocal.set(null);
		}

		if (buf == null) {
			buf = new char[1024];
		}
	}

	public SerializerWriter(Writer writer) {
		this.writer = writer;

		SoftReference<char[]> ref = bufLocal.get();

		if (ref != null) {
			buf = ref.get();
			bufLocal.set(null);
		}

		if (buf == null) {
			buf = new char[1024];
		}
	}

	@Override
	public void write(int c) {
		int newcount = count + 1;
		if (newcount > buf.length) {
			if (writer == null) {
				expandCapacity(newcount);
			} else {
				flush();
				newcount = 1;
			}
		}
		buf[count] = (char) c;
		count = newcount;
	}

	@Override
	public void write(String text) {
		if (text == null) {
			writeNull();
			return;
		}
		write(text, 0, text.length());
	}

	@Override
	public void write(String str, int off, int len) {
		int newcount = count + len;
		if (newcount > buf.length) {
			if (writer == null) {
				expandCapacity(newcount);
			} else {
				do {
					int rest = buf.length - count;
					str.getChars(off, off + rest, buf, count);
					count = buf.length;
					flush();
					len -= rest;
					off += rest;
				} while (len > buf.length);
				newcount = len;
			}
		}
		str.getChars(off, off + len, buf, count);
		count = newcount;
	}

	@Override
	public void write(char[] cbuf, int off, int len) {
		if (off < 0 || off > cbuf.length || len < 0 || off + len > cbuf.length || off + len < 0) {
			throw new IndexOutOfBoundsException();
		} else if (len == 0) {
			return;
		}

		int newcount = count + len;
		if (newcount > buf.length) {
			if (writer == null) {
				expandCapacity(newcount);
			} else {
				do {
					int rest = buf.length - count;
					System.arraycopy(cbuf, off, buf, count, rest);
					count = buf.length;
					flush();
					len -= rest;
					off += rest;
				} while (len > buf.length);
				newcount = len;
			}
		}
		System.arraycopy(cbuf, off, buf, count, len);
		count = newcount;
	}

	@Override
	public void write(char cbuf[]) {
		write(cbuf, 0, cbuf.length);
	}

	@Override
	public void flush() {
		try {
			writer.write(buf, 0, count);
			writer.flush();
		} catch (IOException e) {
			throw new JSONSerializerException(e);
		}
		count = 0;
	}

	@Override
	public void close() {
		if (writer != null && count > 0) {
			flush();
		}
		if (buf.length <= 1024 * 8) {
			bufLocal.set(new SoftReference<char[]>(buf));
		}

		this.buf = null;
	}

	@Override
	public String toString() {
		return new String(buf, 0, count);
	}

	public void writeNull() {
		write("null");
	}

	public void writeInt(int i) {
		if (i == Integer.MIN_VALUE) {
			write("-2147483648");
			return;
		}

		int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);

		int newcount = count + size;
		if (newcount > buf.length) {
			if (writer == null) {
				expandCapacity(newcount);
			} else {
				char[] chars = new char[size];
				getChars(i, size, chars);
				write(chars, 0, chars.length);
				return;
			}
		}

		getChars(i, newcount, buf);

		count = newcount;
	}

	public void writeLong(long i) {
		if (i == Long.MIN_VALUE) {
			write("-9223372036854775808");
			return;
		}

		int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);

		int newcount = count + size;
		if (newcount > buf.length) {
			if (writer == null) {
				expandCapacity(newcount);
			} else {
				char[] chars = new char[size];
				getChars(i, size, chars);
				write(chars, 0, chars.length);
				return;
			}
		}

		getChars(i, newcount, buf);

		count = newcount;
	}

	public void writeByteArray(byte[] bytes) {
		int bytesLen = bytes.length;
		final char quote = '"';

		if (bytesLen == 0) {
			String emptyString = "\"\"";
			write(emptyString);
			return;
		}

		final char[] CA = Base64.CA;

		int eLen = (bytesLen / 3) * 3; // Length of even 24-bits.
		int charsLen = ((bytesLen - 1) / 3 + 1) << 2; // base64 character count
		// char[] chars = new char[charsLen];
		int offset = count;
		int newcount = count + charsLen + 2;
		if (newcount > buf.length) {
			if (writer != null) {
				write(quote);

				for (int s = 0; s < eLen;) {
					// Copy next three bytes into lower 24 bits of int, paying
					// attension to sign.
					int i = (bytes[s++] & 0xff) << 16 | (bytes[s++] & 0xff) << 8 | (bytes[s++] & 0xff);

					// Encode the int into four chars
					write(CA[(i >>> 18) & 0x3f]);
					write(CA[(i >>> 12) & 0x3f]);
					write(CA[(i >>> 6) & 0x3f]);
					write(CA[i & 0x3f]);
				}

				// Pad and encode last bits if source isn't even 24 bits.
				int left = bytesLen - eLen; // 0 - 2.
				if (left > 0) {
					// Prepare the int
					int i = ((bytes[eLen] & 0xff) << 10) | (left == 2 ? ((bytes[bytesLen - 1] & 0xff) << 2) : 0);

					// Set last four chars
					write(CA[i >> 12]);
					write(CA[(i >>> 6) & 0x3f]);
					write(left == 2 ? CA[i & 0x3f] : '=');
					write('=');
				}

				write(quote);
				return;
			}
			expandCapacity(newcount);
		}
		count = newcount;
		buf[offset++] = quote;

		// Encode even 24-bits
		for (int s = 0, d = offset; s < eLen;) {
			// Copy next three bytes into lower 24 bits of int, paying attension
			// to sign.
			int i = (bytes[s++] & 0xff) << 16 | (bytes[s++] & 0xff) << 8 | (bytes[s++] & 0xff);

			// Encode the int into four chars
			buf[d++] = CA[(i >>> 18) & 0x3f];
			buf[d++] = CA[(i >>> 12) & 0x3f];
			buf[d++] = CA[(i >>> 6) & 0x3f];
			buf[d++] = CA[i & 0x3f];
		}

		// Pad and encode last bits if source isn't even 24 bits.
		int left = bytesLen - eLen; // 0 - 2.
		if (left > 0) {
			// Prepare the int
			int i = ((bytes[eLen] & 0xff) << 10) | (left == 2 ? ((bytes[bytesLen - 1] & 0xff) << 2) : 0);

			// Set last four chars
			buf[newcount - 5] = CA[i >> 12];
			buf[newcount - 4] = CA[(i >>> 6) & 0x3f];
			buf[newcount - 3] = left == 2 ? CA[i & 0x3f] : '=';
			buf[newcount - 2] = '=';
		}
		buf[newcount - 1] = quote;
	}

	public void write(char c) {
		int newcount = count + 1;
		if (newcount > buf.length) {
			if (writer == null) {
				expandCapacity(newcount);
			} else {
				flush();
				newcount = 1;
			}
		}
		buf[count] = c;
		count = newcount;
	}

	public void writeBoolean(boolean value) {
		if (value) {
			write("true");
		} else {
			write("false");
		}
	}

	public void writeString(String value) {
		write('\"');
		int length = value.length();
		for (int i = 0; i < length; i++) {
			char ch = value.charAt(i);
			switch (ch) {
			case '\b':
			case '\t':
			case '\r':
			case '\n':
			case '\f':
				// case '\'':
				// case '/':
			case '\"':
			case '\\':
				write('\\');
				write(replaceChars[ch]);
				break;
			default:
				write(ch);
				continue;
			}
		}
		write('\"');
	}

	static void getChars(int i, int index, char[] buf) {
		int q, r;
		int charPos = index;
		char sign = 0;

		if (i < 0) {
			sign = '-';
			i = -i;
		}

		// Generate two digits per iteration
		while (i >= 65536) {
			q = i / 100;
			// really: r = i - (q * 100);
			r = i - ((q << 6) + (q << 5) + (q << 2));
			i = q;
			buf[--charPos] = DigitOnes[r];
			buf[--charPos] = DigitTens[r];
		}

		// Fall thru to fast mode for smaller numbers
		// assert(i <= 65536, i);
		for (;;) {
			q = (i * 52429) >>> (16 + 3);
			r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ...
			buf[--charPos] = digits[r];
			i = q;
			if (i == 0)
				break;
		}
		if (sign != 0) {
			buf[--charPos] = sign;
		}
	}

	static void getChars(long i, int index, char[] buf) {
		long q;
		int r;
		int charPos = index;
		char sign = 0;

		if (i < 0) {
			sign = '-';
			i = -i;
		}

		// Get 2 digits/iteration using longs until quotient fits into an int
		while (i > Integer.MAX_VALUE) {
			q = i / 100;
			// really: r = i - (q * 100);
			r = (int) (i - ((q << 6) + (q << 5) + (q << 2)));
			i = q;
			buf[--charPos] = DigitOnes[r];
			buf[--charPos] = DigitTens[r];
		}

		// Get 2 digits/iteration using ints
		int q2;
		int i2 = (int) i;
		while (i2 >= 65536) {
			q2 = i2 / 100;
			// really: r = i2 - (q * 100);
			r = i2 - ((q2 << 6) + (q2 << 5) + (q2 << 2));
			i2 = q2;
			buf[--charPos] = DigitOnes[r];
			buf[--charPos] = DigitTens[r];
		}

		// Fall thru to fast mode for smaller numbers
		// assert(i2 <= 65536, i2);
		for (;;) {
			q2 = (i2 * 52429) >>> (16 + 3);
			r = i2 - ((q2 << 3) + (q2 << 1)); // r = i2-(q2*10) ...
			buf[--charPos] = digits[r];
			i2 = q2;
			if (i2 == 0)
				break;
		}
		if (sign != 0) {
			buf[--charPos] = sign;
		}
	}

	static int stringSize(int x) {
		for (int i = 0;; i++) {
			if (x <= sizeTable[i]) {
				return i + 1;
			}
		}
	}

	static int stringSize(long x) {
		long p = 10;
		for (int i = 1; i < 19; i++) {
			if (x < p)
				return i;
			p = 10 * p;
		}
		return 19;
	}

	private void expandCapacity(int minimumCapacity) {
		int newCapacity = (buf.length * 3) / 2 + 1;

		if (newCapacity < minimumCapacity) {
			newCapacity = minimumCapacity;
		}
		char newValue[] = new char[newCapacity];
		System.arraycopy(buf, 0, newValue, 0, count);
		buf = newValue;
	}

}
